public class Simulation {

    // gravitational constant
    public static final double G = 6.6743e-11;

    // one astronomical unit (AU) is the average distance of earth to the sun.
    public static final double AU = 150e9;

    // all quantities are based on units of kilogram respectively second and meter.

    // The main simulation method using instances of other classes.
    public static void main(String[] args) {

        CelestialBody sun = new CelestialBody(
            "Sol",
            1.989e30d, // kg
            696340e3d, // meters
            new Vector3(0d, 0d, 0d), // meters
            // sun is the reference point and assumed not to move.
            new Vector3(0d, 0d, 0d),
            StdDraw.YELLOW
        );

        CelestialBody mercury = new CelestialBody(
                "Mercury",
                3.301e23d,
                2.4397e3d,
                // arbitrary initialisation: position opposite to venus with maximal distance
                new Vector3(-46.0e9d, 0d, 0d), // meters
                // viewing from z direction movement is counter-clockwise
                // in meters per second
                new Vector3(0d, 47.87e3d, 0d),
                StdDraw.GRAY
        );

        CelestialBody venus = new CelestialBody(
                "Venus",
                4.867e24d,
                6.0518e3d,
                new Vector3(108e9d, 0d, 0d), // meters
                // viewing from z direction movement is counter-clockwise
                // in meters per second
                new Vector3(0d, -35.02e3d, 0d),
                StdDraw.DARK_GRAY
        );

        CelestialBody earth = new CelestialBody(
            "Earth",
            5.972e24d, // kg
            6.371e3d, // meters
            // (minimal distance to the sun in meters, 0, 0) in meters
            new Vector3(-148e9d, 0d, 0d),
            // viewing from z direction movement is counter-clockwise
            // orbital speed in meters per second (at minimal distance)
            // in meters per second
            new Vector3(0d, 29.29e3d, 0d),
            StdDraw.BLUE
        );

        CelestialBody mars = new CelestialBody(
                "Mars",
                6.417e23,
                3.3962e3,
                new Vector3(230e9d, 0d, 0d), // meters
                new Vector3(0d, -24.007e3d, 0d),
                StdDraw.ORANGE
        );

        CelestialBody[] bodies = new CelestialBody[] {
                sun,
                mercury,
                venus,
                earth,
                mars
        };
        Vector3[] forceOnBody = new Vector3[bodies.length];

        StdDraw.setCanvasSize(500, 500);
        StdDraw.setXscale(-2d * AU, 2d * AU);
        StdDraw.setYscale(-2d * AU, 2d * AU);
        double pixelWidth = 4d * AU / 500d;
        StdDraw.enableDoubleBuffering();
        StdDraw.clear(StdDraw.BLACK);

        double seconds = 0;

        // command line output
        for (CelestialBody body: bodies) {
            body.print();
            System.out.println();
        }

        // simulation loop
        while (true) {

            seconds++; // each iteration computes the movement of the celestial bodies within one second.

            // for each body (with index i): compute the total force exerted on it.
            for (int i = 0; i < bodies.length; i++) {
                forceOnBody[i] = new Vector3(); // begin with zero
                for (int j = 0; j < bodies.length; j++) {
                    if (i == j) continue;
                    Vector3 forceToAdd = bodies[i].gravitationalForce(bodies[j]);
                    forceOnBody[i] = forceOnBody[i].plus(forceToAdd);
                }
            }
            // now forceOnBody[i] holds the force vector exerted on body with index i.

            // for each body (with index i): move it according to the total force exerted on it.
            for (int i = 0; i < bodies.length; i++)
                bodies[i].move(forceOnBody[i]);

            // show all movements in StdDraw canvas only every 3 hours (to speed up the simulation)
            if (seconds % (3 * 3600) == 0) {
                // clear old positions (exclude the following line if you want to draw orbits).
                StdDraw.clear(StdDraw.BLACK);

                // draw new positions
                for (int i = 0; i < bodies.length; i++)
                    bodies[i].draw();

                // show new positions
                StdDraw.show();
            }

        }

    }

}

/**
 * Datenkapselung ist die Praxis, logisch zusammenhängende Einheiten auch
 * zusammenzufassen. Dabei wird eine Klasse definiert, welche seine eigenen
 * Zustände (= private state, auch Attribute genannt), aber auch dazugehörige
 * Methoden, definiert. Wir nennen die Methoden und Attribute einer Klasse
 * Member der Klasse.
 *
 * Data-Hiding ist die Praxis, Member einer Klasse nur bedingt innerhalb
 * in einem Programm zugänglich zu machen. Eine Klasse kann mit 'private'
 * (kommt als keyword vor member definitions) alleiniger Verwalter von
 * Zuständen oder alleiniger Zugriffsberechtigter auf Methoden im Programm
 * sein. Zugriffe von außer einer Klasse ist für private Member einer Klasse
 * nicht mehr möglich.
 *
 */
